<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JS Stack-Based VM</title>
    <style>
        body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; display: flex; flex-direction: column; gap: 20px; padding: 20px; background-color: #f4f4f4; color: #333; }
        .container { display: flex; gap: 20px; width: 100%; max-width: 1400px; margin: 0 auto; }
        .panel { background-color: #fff; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); padding: 15px; }
        .left-panel, .right-panel { flex: 1; display: flex; flex-direction: column; gap: 20px; }
        .code-editor { flex: 1; min-height: 200px; } /* For sourceCodeInput */
        .bytecode-output-textarea { flex: 1; min-height: 100px; } /* For bytecodeOutputInput */
        textarea, pre { width: 100%; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px; padding: 10px; font-family: 'Cascadia Code', 'Consolas', monospace; font-size: 0.9em; background-color: #f9f9f9; }
        .controls { display: flex; gap: 10px; flex-wrap: wrap; }
        button { padding: 8px 15px; border: none; border-radius: 4px; background-color: #007bff; color: white; cursor: pointer; font-size: 0.9em; transition: background-color 0.2s ease; }
        button:hover { background-color: #0056b3; }
        button:disabled { background-color: #cccccc; cursor: not-allowed; }
        h3 { margin-top: 0; color: #007bff; border-bottom: 1px solid #eee; padding-bottom: 5px; }
        .data-display { display: flex; flex-wrap: wrap; gap: 5px; overflow-y: auto; border: 1px solid #ddd; padding: 5px; border-radius: 4px; background-color: #fcfcfc; }
        .data-cell {
            width: 40px; height: 25px; line-height: 25px; text-align: center; border: 1px solid #eee;
            background-color: #f0f0f0; font-size: 0.8em; position: relative;
            display: flex; flex-direction: column; justify-content: center; align-items: center;
            padding-top: 15px;
        }
        .data-cell::before {
            content: attr(data-address);
            position: absolute;
            top: 2px;
            left: 2px;
            font-size: 0.6em;
            color: #888;
        }
        /* 字节码显示 */
        .program-bytecode-output { /* VM's internal program display */
            font-family: 'Cascadia Code', 'Consolas', monospace;
            display: flex; flex-wrap: wrap; gap: 5px;
            border: 1px solid #ddd; padding: 5px; border-radius: 4px; background-color: #fcfcfc;
        }
        .program-bytecode-item { /* Individual items in VM's program display */
            padding: 2px 5px;
            border: 1px solid #eee;
            background-color: #f0f0f0;
            border-radius: 3px;
            font-size: 0.8em;
            display: inline-block; /* Allows wrapping */
            position: relative;
            padding-top: 15px;
        }
        .program-bytecode-item::before {
            content: attr(data-address);
            position: absolute;
            top: 2px;
            left: 2px;
            font-size: 0.6em;
            color: #888;
        }

        .program-bytecode-item.highlight-pc { background-color: #ffda63; border-color: #e0ac00; } /* 当前PC所在指令 */
        .data-cell.highlight-mem { background-color: #a7e4ff; border-color: #4ac6ff; } /* 内存写入 */
        .data-cell.highlight-op-mem { background-color: #ffb3b3; border-color: #ff6666; } /* 内存操作的地址 */

        .stack-display-container {
            display: flex; flex-direction: column-reverse; /* 栈底在下，栈顶在上 */
            gap: 5px; max-height: 250px; overflow-y: auto;
            border: 1px solid #ddd; padding: 5px; border-radius: 4px; background-color: #fcfcfc;
        }
        .stack-item { width: 100%; padding: 5px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; font-size: 0.9em; background-color: #e6ffe6; border-radius: 3px; }
        .stack-item:last-child { border-bottom: none; }
        .stack-item strong { color: #28a745; }
        .console-output { flex: 1; min-height: 100px; background-color: #333; color: #00ff00; padding: 10px; font-size: 0.9em; overflow-y: auto; white-space: pre-wrap; word-break: break-all; }
        .current-operation { background-color: #e6f7ff; border: 1px solid #91d5ff; padding: 10px; border-radius: 4px; font-family: 'Cascadia Code', monospace; font-size: 1.1em; color: #0056b3; text-align: center; }
        .error { color: red; font-weight: bold; }
    </style>
</head>
<body>
    <h1>JavaScript Stack-Based VM</h1>

    <div class="container top-section">
        <div class="panel left-panel">
            <h3>汇编代码输入 (ASM)</h3>
            <textarea id="sourceCodeInput" class="code-editor" rows="15">
; 这是一个汇编程序示例
; 将两个数相加并打印结果

    PUSH 10     ; 将 10 压入栈
    PUSH 20     ; 将 20 压入栈
    ADD         ; 将栈顶两个数相加 (20 + 10)，结果 30 压入栈
    PRINT       ; 打印栈顶的数 (30)
    HALT        ; 停止虚拟机

; 您也可以使用别名来书写
; PUSH 10
; PUSH 20
; +           ; 等同于 ADD
; PRINT
; HALT

; 位运算示例: (10 & 5) | (~3)
; 预期输出: ((1010_2 & 0101_2) | (~0011_2)) = (0000_2 | (~3)) = (0 | -4) = -4
    PUSH 10
    PUSH 5
    &           ; 等同于 AND
    PUSH 3
    ~           ; 等同于 NOT
    |           ; 等同于 OR
    PRINT
    HALT
            </textarea>
            <div class="controls">
                <button id="compileBtn">编译到字节码</button>
            </div>

            <h3>字节码输出 (用于虚拟机)</h3>
            <textarea id="bytecodeOutputInput" class="bytecode-output-textarea" rows="8" readonly>1 10 1 20 3 18 19</textarea>
            <div class="controls">
                <button id="loadBytecodeBtn">载入虚拟机</button>
            </div>
        </div>

        <div class="panel right-panel">
            <h3>虚拟机控制台</h3>
            <div class="controls">
                <button id="runProgramBtn">运行</button>
                <button id="stepProgramBtn">单步</button>
                <button id="resetVMBtn">重置</button>
            </div>
            <div id="cycleCountDisplay" class="current-operation" style="font-size: 1em; color: #333; background-color: #f0f0f0; border-color: #ddd; margin-bottom: 10px;">
                周期数: 0
            </div>
            <div id="currentOperation" class="current-operation">
                等待程序载入...
            </div>
            <div id="consoleOutput" class="console-output"></div>
        </div>
    </div>

    <div class="container bottom-section">
        <div class="panel" style="flex: 2;">
            <h3>程序区 (已载入字节码)</h3>
            <div id="programDisplay" class="program-bytecode-output"></div>
        </div>
        <div class="panel" style="flex: 2;">
            <h3>内存</h3>
            <div id="memoryDisplay" class="data-display"></div>
        </div>
        <div class="panel" style="flex: 1;">
            <h3>栈</h3>
            <div id="stackDisplay" class="stack-display-container"></div>
        </div>
    </div>

    <script src="vm.js"></script>
    <script>
        // UI 元素引用
        const sourceCodeInput = document.getElementById('sourceCodeInput');
        const bytecodeOutputInput = document.getElementById('bytecodeOutputInput');
        const programDisplay = document.getElementById('programDisplay');
        const memoryDisplay = document.getElementById('memoryDisplay');
        const stackDisplay = document.getElementById('stackDisplay');
        const currentOperationDisplay = document.getElementById('currentOperation');
        const consoleOutput = document.getElementById('consoleOutput');
        const cycleCountDisplay = document.getElementById('cycleCountDisplay'); // 获取周期计数显示元素

        const compileBtn = document.getElementById('compileBtn');
        const loadBytecodeBtn = document.getElementById('loadBytecodeBtn');
        const runProgramBtn = document.getElementById('runProgramBtn');
        const stepProgramBtn = document.getElementById('stepProgramBtn');
        const resetVMBtn = document.getElementById('resetVMBtn');

        const vm = new StackVM();

        // UI 更新函数
        function updateUI(vmState) {
            // 更新内存显示
            memoryDisplay.innerHTML = '';
            vmState.memory.forEach((value, index) => {
                const cell = document.createElement('div');
                cell.className = 'data-cell';
                cell.title = `Memory[${index}] = ${value}`;
                cell.textContent = value;
                cell.setAttribute('data-address', index);

                // Highlight last written memory address
                if (index === vmState.lastMemWriteAddr) {
                    cell.classList.add('highlight-mem');
                }
                
                // Highlight memory address being operated on by LOAD/STORE (fixed address)
                // 确保使用新的 OPCODES 值
                if (vmState.currentInstruction === vm.OPCODES.LOAD || vmState.currentInstruction === vm.OPCODES.STORE) {
                    if (vmState.currentOperand === index) {
                        cell.classList.add('highlight-op-mem');
                    }
                }
                memoryDisplay.appendChild(cell);
            });

            // 更新栈显示
            stackDisplay.innerHTML = '';
            if (vmState.stack.length === 0) {
                stackDisplay.innerHTML = '<div style="padding: 10px; color: #888;">栈为空</div>';
            } else {
                vmState.stack.slice().reverse().forEach((value, index) => {
                    const item = document.createElement('div');
                    item.className = 'stack-item';
                    if (index === 0) { // 最顶部的元素是栈顶
                        item.innerHTML = `<span>[${vmState.stack.length - 1 - index}]</span> <strong>${value} (栈顶)</strong>`;
                    } else {
                        item.innerHTML = `<span>[${vmState.stack.length - 1 - index}]</span> ${value}`;
                    }
                    stackDisplay.appendChild(item);
                });
            }
            
            // 更新周期计数显示
            cycleCountDisplay.textContent = `周期数: ${vmState.cycleCount}`;

            // 更新当前操作显示
            let currentOpText = "等待程序载入...";
            if (vmState.program.length > 0) {
                if (vmState.pc < vmState.program.length) {
                    const opcode = vmState.program[vmState.pc];
                    const mnemonic = Object.keys(vm.OPCODES).find(key => vm.OPCODES[key] === opcode) || `UNKNOWN(${opcode})`;
                    
                    let opDisplay = `${mnemonic}`;
                    // 确保使用新的 OP_ARG_COUNTS 值
                    if (vm.OP_ARG_COUNTS[opcode] > 0) {
                        const operand = vmState.program[vmState.pc + 1];
                        opDisplay += ` ${operand}`;
                    }
                    currentOpText = `PC: ${vmState.pc} | 指令: ${opDisplay}`;
                } else if (!vmState.isRunning) {
                    currentOpText = "程序执行完毕。";
                }
            } else {
                currentOpText = "未载入程序。";
            }
            if (!vmState.isRunning && vmState.program.length > 0 && vmState.pc < vmState.program.length) {
                if (vmState.pc < vmState.program.length) {
                    currentOpText = "程序暂停。 " + currentOpText;
                }
            }
            currentOperationDisplay.textContent = currentOpText;

            // 更新程序区（字节码）显示并高亮当前PC
            updateProgramDisplay(vmState.program, vmState.pc);

            // 更新按钮状态
            runProgramBtn.disabled = !vmState.program.length || vmState.isRunning || vmState.pc >= vmState.program.length;
            stepProgramBtn.disabled = !vmState.program.length || vmState.isRunning || vmState.pc >= vmState.program.length;
            resetVMBtn.disabled = !vmState.program.length;
            compileBtn.disabled = vmState.isRunning;
            loadBytecodeBtn.disabled = vmState.isRunning || !bytecodeOutputInput.value.trim();
        }

        // 更新程序区显示，并高亮PC
        function updateProgramDisplay(program, currentPc) {
            programDisplay.innerHTML = '';
            if (program.length === 0) {
                programDisplay.textContent = '';
                return;
            }

            program.forEach((value, index) => {
                const item = document.createElement('span');
                item.className = 'program-bytecode-item';
                item.setAttribute('data-address', index);

                const mnemonic = Object.keys(vm.OPCODES).find(key => vm.OPCODES[key] === value);
                let displayValue = mnemonic || value; 
                item.textContent = displayValue; 

                if (index === currentPc) {
                    item.classList.add('highlight-pc');
                }
                programDisplay.appendChild(item);
            });
        }

        // VM的输出回调
        vm.onOutput = (value) => {
            consoleOutput.textContent += `${value}\n`;
            consoleOutput.scrollTop = consoleOutput.scrollHeight;
        };

        // VM的状态变化回调
        vm.onStateChange = (state) => {
            updateUI(state);
        };

        // 事件监听：载入机器码 (现在从 bytecodeOutputInput 获取)
        loadBytecodeBtn.addEventListener('click', () => {
            try {
                consoleOutput.textContent = ''; // 清空控制台

                const rawInput = bytecodeOutputInput.value;
                const programArray = rawInput
                    .split(/\s+/) 
                    .filter(token => token.length > 0)
                    .map(token => {
                        if (token.startsWith('0x')) {
                            return parseInt(token.slice(2), 16);
                        }
                        return parseInt(token, 10);
                    });

                if (programArray.some(isNaN)) {
                    throw new Error("字节码包含无效的非数字字符。请确保只有数字和0x前缀的十六进制数。");
                }
                
                vm.loadProgram(programArray);
                vm.onOutput?.("Bytecode loaded successfully into VM.");
                updateUI(vm.getVMState());
            } catch (e) {
                console.error("加载字节码错误:", e);
                vm.onOutput?.(`ERROR: ${e.message}\n`);
            }
        });

        // 编译按钮事件监听
        compileBtn.addEventListener('click', () => {
            try {
                consoleOutput.textContent = '';
                const source = sourceCodeInput.value;
                const compiledBytecode = assemble(source);
                bytecodeOutputInput.value = compiledBytecode.join(' ');
                vm.onOutput?.("Compilation successful! Bytecode displayed below.");
                updateUI(vm.getVMState());
            } catch (e) {
                console.error("汇编错误:", e);
                vm.onOutput?.(`ASSEMBLY ERROR: ${e.message}\n`);
                bytecodeOutputInput.value = '';
            }
        });


        runProgramBtn.addEventListener('click', () => {
            vm.run();
        });

        stepProgramBtn.addEventListener('click', () => {
            try {
                vm.isRunning = true; 
                vm.step();
            } catch (e) {
                console.error("VM Step Error:", e.message);
            } finally {
                vm.isRunning = false;
                vm.onStateChange?.(vm.getVMState());
            }
        });

        resetVMBtn.addEventListener('click', () => {
            vm.reset();
            consoleOutput.textContent = '';
            updateUI(vm.getVMState());
        });

        // 初始UI状态
        updateUI(vm.getVMState());

    </script>
</body>
</html>